﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.Windows;
using PSE.Framework.UI.Common.EnumWrapper;
using PSE.Framework.UI.Common.EnumWrapper.WeakEventManagers;
using PSE.Framework.UI.Resources;

namespace PSE.Framework.UI.Common
{
    public interface IEnumWrapper
    {
        string Text { get; }
        object Value { get; }
    }

    /// <summary>
    /// Encapsula um enum para convertê-lo para texto
    /// </summary>
    /// <typeparam name="T">Type do enum</typeparam>
    [Serializable]
    [DebuggerDisplay("Value = {Value}, Text = {Text}")]
    public sealed class EnumWrapper<T> : IEnumWrapper, INotifyPropertyChanged, IComparable, IComparable<EnumWrapper<T>>, IEquatable<EnumWrapper<T>>, IWeakEventListener
        where T : struct
	{
        [NonSerialized]
		private IResourceProvider _provider;

        #region Construtor
        /// <summary>
        /// Construtor padrão
        /// </summary>
        private EnumWrapper()
        {
            this.CreateProvider();
        }

        /// <summary>
        /// Construtor que inicializa Value
        /// </summary>
        /// <param name="value">Valor do enum</param>
        public EnumWrapper(T value)
            : this()
        {
            if (!(value is Enum))
                throw new InvalidCastException(value.ToString() + " is not a Enum");

            _value = value;
            Text = RetrieveResource();
        }
        #endregion

        #region Value
        private readonly T _value;
        /// <summary>
        /// Valor do enum
        /// </summary>
        public T Value
        {
            get { return _value; }
        }
        #endregion

        #region Text
        private string _text;
        /// <summary>
        /// Texto do enum
        /// </summary>
        public string Text
        {
            get
            {
                if (String.IsNullOrEmpty(_text))
                    _text = RetrieveResource();
                return _text;
            }
            private set
            {
                _text = value;
                OnPropertyChanged("Text");
            }
        }
        #endregion

        #region PropertyChanged

        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region RetrieveResource
        private string RetrieveResource()
        {
            foreach (FieldInfo info in _value.GetType().GetFields())
            {
                if (!info.IsSpecialName)
                {
                    if (_value.ToString() == info.GetValue(null).ToString())
                    {
                        object[] attributes = info.GetCustomAttributes(typeof(EnumWrapperKey), true);
                        if (attributes != null && attributes.Length > 0)
                        {
                            foreach (EnumWrapperKey att in attributes)
                                return (string)_provider.TryGetResource(att.Key);
                        }
                    }
                }
            }
            return string.Empty;
        }

        private void Provider_ResourceChanged(object sender, EventArgs e)
        {
            Text = RetrieveResource();
        }
        #endregion

        #region CreateProvider
        private void CreateProvider()
        {
            _provider = ResourceProviderManager.GetResourceProvider();

            ///Adicionar um listener para o evento ResourceChanged do provider (WeakEvent), alteração realizada para 
            ///resolver parte do problema de Memory Leak da aplicação
            ResourceChangedEventManager.AddListener(_provider, this);
        }
        #endregion

        #region OnDeserialized
        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            this.CreateProvider();
        }
        #endregion

        #region Equals
        public override bool Equals(object obj)
        {
            if (obj is EnumWrapper<T>)
            {
                return this.Equals((EnumWrapper<T>)obj);
            }
            else
            {
                return false;
            }
        }
        #endregion

        //#region ToString
        //public override string ToString()
        //{
        //    return Text;
        //}
        //#endregion

        #region GetHashCode
        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }
        #endregion

        #region Operators

        #region Equality
        public static bool operator ==(EnumWrapper<T> a, EnumWrapper<T> b)
        {
            if ((object)a == null && (object)b == null)
                return true;
            else if ((object)a == null || (object)b == null)
                return false;
            else
                return Convert.ToInt32(a.Value) == Convert.ToInt32(b.Value);
        }
        #endregion

        #region Inequality
        public static bool operator !=(EnumWrapper<T> a, EnumWrapper<T> b)
        {
            return !(a == b);
        }
        #endregion

        #region GreaterThan
        public static bool operator >(EnumWrapper<T> a, EnumWrapper<T> b)
        {
            return string.Compare((a != null ? a.Text : null), (b != null ? b.Text : null)) > 0;
        }
        #endregion

        #region LessThan
        public static bool operator <(EnumWrapper<T> a, EnumWrapper<T> b)
        {
            return string.Compare((a != null ? a.Text : null), (b != null ? b.Text : null)) < 0;
        }
        #endregion

        #endregion

        #region Cast

        #region Enum -> EnumWrapper
        public static implicit operator EnumWrapper<T>(T value)
        {
            return new EnumWrapper<T>(value);
        }
        #endregion
        

        #region EnumWrapper -> Enum
        public static explicit operator T(EnumWrapper<T> value)
        {
            if (value != null)
                return value.Value;
            else
                return default(T);
        }
        #endregion

        #region EnumWrapper -> Enum
        public static explicit operator T?(EnumWrapper<T> value)
        {
            if (value != null)
                return value.Value;
            else
                return null;
        }
        #endregion

        //#region EnumWrapper -> String
        //public static explicit operator String(EnumWrapper<T> value)
        //{
        //    return value.Text;
        //}
        //#endregion

        #region EnumWrapper -> Int32
        public static explicit operator Int32(EnumWrapper<T> value)
        {
            if (value != null)
                return Convert.ToInt32(value.Value);
            else
                return Convert.ToInt32(default(T));
        }
        #endregion

        #endregion

        #region IComparable Members

        public int CompareTo(object obj)
        {
            return this.CompareTo((EnumWrapper<T>)obj);
        }

        #endregion

        #region IComparable<EnumWrapper<T>> Members

        public int CompareTo(EnumWrapper<T> other)
        {
            if (this > other)
            {
                return 1;
            }
            else if (this < other)
            {
                return -1;
            }
            else
            {
                return 0;
            }
        }

        #endregion

        #region IEquatable<EnumWrapper<T>> Members

        public bool Equals(EnumWrapper<T> other)
        {
            return this == other;
        }

        #endregion

        #region GetEnums
        /// <summary>
        /// Retorna uma coleção de EnumWrappers com todos os enums
        /// </summary>
        /// <returns>Coleção de EnumWrappers</returns>
        public static ObservableCollection<EnumWrapper<T>> GetEnums()
        {
            ObservableCollection<EnumWrapper<T>> enums = new ObservableCollection<EnumWrapper<T>>();

            foreach (T v in Enum.GetValues(typeof(T)))
            {
                enums.Add(new EnumWrapper<T>(v));
            }

            return enums;
        }

        /// <summary>
        /// Retorna uma coleção de EnumWrappers com os enums especificados
        /// </summary>
        /// <param name="value">Valor dos enums</param>
        /// <returns>Coleção de EnumWrappers</returns>
        public static ObservableCollection<EnumWrapper<T>> GetEnums(params T[] value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            ObservableCollection<EnumWrapper<T>> enums = new ObservableCollection<EnumWrapper<T>>();

            foreach (T v in value)
            {
                enums.Add(new EnumWrapper<T>(v));
            }

            return enums;
        }
        #endregion

        #region IEnumWrapper Members

        string IEnumWrapper.Text
        {
            get { return this.Text; }
        }

        object IEnumWrapper.Value
        {
            get { return this.Value; }
        }

        #endregion

        #region IWeakEventListener Members
        /// <summary>
        /// Receber o WeakEvent e executar o método de tratamento do evento
        /// </summary>
        /// <param name="managerType"></param>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <returns></returns>
        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if (managerType == typeof(ResourceChangedEventManager))
            {                
                Provider_ResourceChanged(sender, e);
            }
            else
            {
                return false;
            }
            return true;
        }

        #endregion
    }
}